package me.dqn;

import me.dqn.annotation.Subscriber;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author dqn
 * created at 2018/8/3 - 15:59
 * Evenbus 的简单实现。
 */
public class EventBus {
    String identifier;
    List<Object> subscribers = new ArrayList<>(16);
    Executor executor;

    public EventBus(Executor executor) {
        this.identifier = "EventBus" + Instant.now().toString();
        this.executor = executor;
    }

    /**
     * construct bus with identifier
     *
     * @param id
     */
    public EventBus(String id) {
        this.identifier = id;
    }

    /**
     * subscribe to bus
     *
     * @param object
     */
    public void register(Object object) {
        this.subscribers.add(object);
    }

    /**
     * remove subscriber from list.
     *
     * @param object
     */
    public void unRegister(Object object) {
        this.subscribers.remove(object);
    }

    /**
     * put object event to bus.
     * post event to subscriber synchronous.
     *
     * @param event
     */
    public void post(Object event) {
        subscribers.stream()
                .forEach(subscriber -> dealWithEvent(subscriber, event, false));
    }

    /**
     * post event to subscriber asynchronous.
     *
     * @param event
     */
    public void postAsyc(Object event) {
        if (executor == null) {
            executor = new ThreadPoolExecutor(
                    4,
                    32,
                    200,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingDeque<>()
            );
        }
        subscribers.forEach(subscriber -> dealWithEvent(subscriber, event, true));
    }

    /**
     * actually deal do the job
     *
     * @param subscriber subscriber
     * @param event      event
     * @param isAsync    is doing job in async mode.
     */
    public void dealWithEvent(Object subscriber, Object event, boolean isAsync) {
        Method[] methods = subscriber.getClass().getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(Subscriber.class)) {
                Parameter[] parameters = method.getParameters();
                if (parameters[0].getParameterizedType().equals(event.getClass())) {
                    if (isAsync) {
                        executor.execute(() -> {
                            try {
                                method.invoke(subscriber, event);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        });
                    } else {
                        try {
                            method.invoke(subscriber, event);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    @Override
    public String toString() {
        return "EventBus{" +
                "identifier='" + identifier + '\'' +
                '}';
    }
}
